#!/user/bin/env python

#  ESPRESSIF MIT License
#
#  Copyright (c) 2020 <ESPRESSIF SYSTEMS (SHANGHAI) CO., LTD>
#
#  Permission is hereby granted for use on all ESPRESSIF SYSTEMS products, in which case,
#  it is free of charge, to any person obtaining a copy of this software and associated
#  documentation files (the "Software"), to deal in the Software without restriction, including
#  without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
#  and/or sell copies of the Software, and to permit persons to whom the Software is furnished
#  to do so, subject to the following conditions:
#
#  The above copyright notice and this permission notice shall be included in all copies or
#  substantial portions of the Software.
#
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
#  FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
#  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
#  IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
#  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""
mk_audio_tone version 1.2:

This script is used to pack mp3 and wav files into one binary file.
version 1.2 provied more configurable ways to generate the audio bin, and support the new format of it.

1. command parameters to change the configuration:
    - '-h': Get help about the parameters.
    - '-f': Base folder to contains the source files genrated by this script, force required.
    - '-r': Folder of the music resources, force required.
    - '-c': Name of the source file generated by this script, default: 'audio_tone_uri.c'
    - '-H': Name of the header file generated by this script, default: 'audio_tone_uri.h'.
    - '-t': Name of the target file, default: 'audio_tone.bin'.
    - '-F': Format of the target file, default: 1, please refer to #2 for more details about the `format` of the audio bin.
    - '-v': Version of the audio bin, controlled by users.

2. Format of audio bin
    - Audio bin structure:

        |-----------------------------------------|
        |                  Headers                |
        |-----------------------------------------|
        |   `esp_app_desc_t` struct (optional)    |
        |-----------------------------------------|
        |                file table               |
        |-----------------------------------------|
        |                  files                  |
        |-----------------------------------------|
        |               crc (optional)            |
        |-----------------------------------------|
        |              tail (optional)            |
        |-----------------------------------------|

    - Format 0: no 'esp_app_desc_t' struct, crc and tail.
    - Format 1: 'esp_app_desc_t' struct, crc and tail are contained.

"""

import sys
import os
import struct
import argparse
import time

__version__ = '1.2'

SRC_FILE_NAME = 'audio_tone_uri.c'
HEADER_FILE_NAME = 'audio_tone_uri.h'
TARGET_FILE_NAME = 'audio_tone.bin'

def save_2_file(folder, dst, buffer):
    """
    Save 'buffer' to file
    """
    if len(folder) > 0 and not os.path.exists(folder):
        os.makedirs(folder)
    file_path = folder + '/' + dst
    with open(file_path,'wb') as f:
        if f != None:
            f.write(buffer)
            f.close()

def gen_c_file(file_list):
    """
    generate the c souere file for audio tone
    """
    c_file = ''
    c_file += '/*This is tone file*/\r\n\r\nconst char* tone_uri[] = {\r\n'

    def gen_line(idx, name):
        return '   "flash://tone/' + str(idx) + '_' + name + '",\r\n'

    for line in map(gen_line, range(len(file_list)), file_list):
        c_file += line

    c_file += '};\r\n\r\n'
    c_file += 'int get_tone_uri_num()\r\n{\r\n    return sizeof(tone_uri) / sizeof(char *) - 1;\r\n}\r\n'

    return c_file

def gen_h_file(file_list):
    """
    generate the c header file for audio tone
    """
    h_file = ''
    h_file += '#ifndef __AUDIO_TONEURI_H__\r\n#define __AUDIO_TONEURI_H__\r\n\r\n'
    h_file += 'extern const char* tone_uri[];\r\n\r\n'
    h_file += 'typedef enum {\r\n'

    for line in ['    TONE_TYPE_' + name.split(".")[0].upper() + ',\r\n' for name in file_list]:
        h_file += line

    h_file += '    TONE_TYPE_MAX,\r\n} tone_type_t;\r\n\r\nint get_tone_uri_num();\r\n\r\n#endif\r\n'

    return h_file

def gen_source_code(file_list):
    """
    generate the c files for the audio tone
    """
    return { 'source' : gen_c_file(file_list), 'header' : gen_h_file(file_list) }

def pack_tone_header(tone_bin, header, file_cnt, b_format):
    """
    insert 'header' and other information defined by espressif into tone bin.
    """
    tone_bin += struct.pack("<HHI", header, file_cnt, b_format)
    return tone_bin

def pack_tone_app_desc(tone_bin, b_format, b_ver):
    class app_desc(object):
        """
        Please refer to esp_image_format.h for more details about 'esp_app_desc_t'.
        """
        def __init__(self, magic_word, project_name, ver):
            self.__magic_word = magic_word
            self.__secure_version = 0
            self.__reserved1 = [ 0 ] * 2
            self.__version = ver
            self.__project_name = project_name
            self.__time = time.strftime("%H:%M:%S", time.localtime())
            self.__date = time.strftime("%Y-%m-%d", time.localtime())
            self.__idf_ver = ''
            self.__app_elf_sha256 = b'\x00' * 32
            self.__reserved2 = [ 0 ] * 20

        def pack_into_bin(self, dst):
            dst += struct.pack("<I",    self.__magic_word)
            dst += struct.pack("<I",    self.__secure_version)
            dst += struct.pack("<2I",   *self.__reserved1)
            dst += struct.pack("<32s",  self.__version)
            dst += struct.pack("<32s",  self.__project_name)
            dst += struct.pack("<16s",  self.__time)
            dst += struct.pack("<16s",  self.__date)
            dst += struct.pack("<32s",  self.__idf_ver)
            dst += struct.pack("<32c",  *self.__app_elf_sha256)
            dst += struct.pack("<20I",  *self.__reserved2)
            return dst

    if b_format == 1:
        desc = app_desc(0xF55F9876, 'ESP_TONE_BIN', b_ver)
        tone_bin = desc.pack_into_bin(tone_bin)

    return tone_bin

def pack_tone_file_table(tone_bin, file_list):
    """
    pack the file table
    """
    RFU = [0]*12
    next_addr = len(tone_bin) + 64 * len(file_list)

    def get_info(file_list, offset):
        file_type = { 'mp3' : 0, 'wav' : 1}
        idx = 0
        for f in file_list:
            f_size = os.path.getsize(f)
            info = {'name' : f, 'idx' : idx, 'type' : file_type[f.split('.')[-1].lower()], 'addr': offset, 'len' : f_size }
            yield info
            idx += 1
            offset += f_size + ((4 - f_size % 4) % 4)

    for info in get_info(file_list, next_addr):
        tone_bin += struct.pack("<BBBBII", 0x28           #file tag
                                        , info['idx']     #song index
                                        , info['type']
                                        , 0x0             #songVer
                                        , info['addr']
                                        , info['len']     #song length
                                        )
        tone_bin += struct.pack("<12I",*RFU)
        tone_bin += struct.pack("<I",0x0)

        print ('fname:', info['name'])
        print ('song index: ', info['idx'])
        print ('file type: ', info['type'])
        print ('songAddr: ', info['addr'])
        print ('songLen: ', info['len'])
        print ('bin len:',len(tone_bin))
        print ('--------------------')
        print ('')

    return tone_bin

def pack_tone_file(tone_bin, file_list):
    """
    pack the files
    """
    for f_name in file_list:
        with open(f_name, 'rb') as f:
            f_size = os.path.getsize(f_name)
            tone_bin += f.read()
            tone_bin += b'\xff' * (( 4 - f_size % 4) % 4)

    return tone_bin

def pack_tone_crc(tone_bin, b_format):
    """
    pack the crc
    """
    if b_format == 1:
        import zlib
        tone_bin += struct.pack("<i", zlib.crc32(tone_bin))
    return tone_bin

def pack_tone_tail(tone_bin, b_format):
    """
    pack fixed tail
    """
    if b_format == 1:
        tone_bin += struct.pack("<H", 0xDFAC)
    return tone_bin

def pack_tone_bin(resource_folder, file_list, b_format, b_ver):
    """
    pack the files in the 'file_list' into a buffer with the format defined by espressif.
    """
    cur_dir = os.getcwd()
    os.chdir(resource_folder)

    tone_bin = b''
    tone_bin = pack_tone_header(tone_bin, 0x2053, len(file_list), b_format)
    tone_bin = pack_tone_app_desc(tone_bin, b_format, b_ver)
    tone_bin = pack_tone_file_table(tone_bin, file_list)
    tone_bin = pack_tone_file(tone_bin, file_list)
    tone_bin = pack_tone_crc(tone_bin, b_format)
    tone_bin = pack_tone_tail(tone_bin, b_format)

    os.chdir(cur_dir)

    return tone_bin

if __name__ == '__main__':

    argparser = argparse.ArgumentParser()
    argparser.add_argument('-f', '--folder', type=str, required=True ,help='base folder for the source files generated')
    argparser.add_argument('-c', '--cfile', type=str, default=SRC_FILE_NAME, help='c source file name')
    argparser.add_argument('-H', '--hfile', type=str, default=HEADER_FILE_NAME, help='c header file name')
    argparser.add_argument('-r', '--resources', type=str, required=True, help='the folder where the music resources located')
    argparser.add_argument('-t', '--target', type=str, default=TARGET_FILE_NAME, help='target file name ')
    argparser.add_argument('-F', '--format', type=int, default=1, choices=[0, 1], help='bin format 0: v1, 1: v2 which contains the `esp_app_desc_t` behind header')
    argparser.add_argument('-v', '--version', type=str, default='v1.0', help='file version, controlled by user')
    args = argparser.parse_args()

    if raw_input('The bin version will be: %s\r\nContinue?(y/N): ' % (args.version)).lower() == 'y':
        file_list = [x for x in os.listdir(args.resources) if ((x.endswith(".wav")) or (x.endswith(".mp3")))]
        file_list.sort()

        print ('__version__: ', __version__)
        print ('file_list: ', file_list)
        print ('--------------------')

        source = gen_source_code(file_list)
        save_2_file(args.folder, args.cfile, bytearray(source['source'], encoding='utf-8'))
        save_2_file(args.folder, args.hfile, bytearray(source['header'], encoding='utf-8'))

        tone_bin = pack_tone_bin(args.resources, file_list, args.format, args.version)
        save_2_file(args.resources, args.target, tone_bin)

        print ('Target generated into %s\r\n' % (args.resources + '/' + args.target))
    else:
        print ('Operation abort!')
